package raft

import "time"

// InstallSnapshotArgs 是发送给其他节点的安装快照的参数
type InstallSnapshotArgs struct {
	Term              int    // Leader 的任期号
	LeaderId          int    // Leader 的 ID，以便于跟随者重定向请求
	LastIncludedIndex int    // 快照中包含的最后日志条目的索引值
	LastIncludedTerm  int    // 快照中包含的最后日志条目的任期号
	Offset            int    // 快照数据在整个快照中的偏移量
	Data              []byte // 快照数据
	Done              bool   // 是否是最后一个分块
}

// InstallSnapshotReply 是发送给其他节点的安装快照的回复
type InstallSnapshotReply struct {
	Term int // 当前节点的任期号
}

// InstallSnapshot 是接收其他节点发送的安装快照的 RPC
func (rf *Raft) InstallSnapshot(args *InstallSnapshotArgs, reply *InstallSnapshotReply) error {
	rf.mu.Lock()
	defer rf.mu.Unlock()
	defer rf.persist()

	// 获取参数
	leaderTerm := args.Term
	snapshot := args.Data
	done := args.Done
	lastIncludedIndex := args.LastIncludedIndex
	lastIncludedTerm := args.LastIncludedTerm

	// 如果 Leader 的任期号小于当前节点的任期号，返回当前节点的任期号
	if leaderTerm < rf.currentTerm {
		reply.Term = rf.currentTerm
		return nil
	}

	// 如果 Leader 的任期号大于当前节点的任期号，更新当前节点的任期号，并重置 votedFor
	if leaderTerm > rf.currentTerm {
		rf.currentTerm = leaderTerm
		rf.votedFor = -1
	}

	// 将当前节点的状态改为 Follower，并更新 lastHeartbeat
	rf.state = Follower
	rf.lastHeartbeat = time.Now()
	reply.Term = rf.currentTerm

	// 如果快照中包含的最后日志条目的索引值小于等于当前节点提交的最后日志条目的索引值，返回
	if lastIncludedIndex <= rf.commitIndex {
		return nil
	}

	// 如果是最后一个分块，将快照应用到状态机中
	if done {
		applyMsg := ApplyMsg{
			SnapshotValid: true,
			Snapshot:      snapshot,
			SnapshotTerm:  lastIncludedTerm,
			SnapshotIndex: lastIncludedIndex,
		}
		rf.mu.Unlock()
		rf.applyCh <- applyMsg
		rf.mu.Lock()
	}
	return nil
}

// CondInstallSnapshot
// A service wants to switch to snapshot. Only do so if Raft hasn't
// had more recent info since it communicate the snapshot on applyCh.
// CondInstallSnapshot是一个条件安装快照的函数，如果满足条件，就会安装快照
func (rf *Raft) CondInstallSnapshot(lastIncludedTerm int, lastIncludedIndex int, snapshot []byte) bool {
	rf.mu.Lock()
	defer rf.mu.Unlock()
	defer rf.persist()

	// 如果快照中包含的最后日志条目的索引值小于等于当前节点提交的最后日志条目的索引值，返回 false
	if lastIncludedIndex <= rf.commitIndex {
		return false
	}

	// 构造快照的日志条目
	log := make([]LogEntry, 0)
	log = append(log, LogEntry{
		Command: -1,
		Term:    lastIncludedTerm,
		Index:   lastIncludedIndex,
	})

	// 如果快照中包含的最后日志条目的索引值小于等于当前节点最后一条日志的索引值，并且最后一条日志的任期号等于快照中包含的最后日志条目的任期号
	if lastIncludedIndex <= rf.GetLogEntry(-1).Index && rf.GetLogEntry(lastIncludedIndex).Term == lastIncludedTerm {
		// 从当前节点的日志中删除快照中包含的最后日志条目之前的所有日志条目
		rf.log = append(log, rf.log[rf.GetIndex(lastIncludedIndex)+1:]...)
	} else {
		// 如果不满足上述条件，直接用快照的日志条目替换当前节点的日志
		rf.log = log
	}

	// 更新当前节点的快照和提交索引
	rf.snapshot = snapshot
	rf.lastApplied = lastIncludedIndex
	rf.commitIndex = lastIncludedIndex
	return true
}

// Snapshot
// the service says it has created a snapshot that has
// all info up to and including index. this means the
// service no longer needs the log through (and including)
// that index. Raft should now trim its log as much as possible.
// Snapshot是接收客户端发送的创建快照的 RPC
func (rf *Raft) Snapshot(index int, snapshot []byte) {
	rf.mu.Lock()
	defer rf.mu.Unlock()
	defer rf.persist()

	// 如果 index 小于当前节点的快照索引，返回
	if index < rf.log[0].Index {
		return
	}

	// 构造快照的日志条目
	log := make([]LogEntry, 0)
	log = append(log, LogEntry{
		Command: -1,
		Term:    rf.GetLogEntry(index).Term,
		Index:   rf.GetLogEntry(index).Index,
	})

	// 从当前节点的日志中删除快照之前的所有日志条目
	rf.log = append(log, rf.log[rf.GetIndex(index+1):]...)

	// 更新当前节点的快照
	rf.snapshot = snapshot
}

// sendInstallSnapshot 向指定的节点发送安装快照的 RPC
func (rf *Raft) sendInstallSnapshot(server int) {
	rf.mu.RLock()
	// 如果当前节点不是 Leader，直接返回
	if rf.state != Leader {
		rf.mu.RUnlock()
		return
	}

	// 构造安装快照的参数
	installSnapshotArgs := InstallSnapshotArgs{
		Term:              rf.currentTerm,  // Leader 的任期号
		LeaderId:          rf.me,           // Leader 的 ID
		LastIncludedIndex: rf.log[0].Index, // 快照中包含的最后日志条目的索引值
		LastIncludedTerm:  rf.log[0].Term,  // 快照中包含的最后日志条目的任期号
		Offset:            0,               // 快照数据在整个快照中的偏移量
		Data:              rf.snapshot,     // 快照数据
		Done:              true,            // 是否是最后一个分块
	}
	installSnapshotReply := InstallSnapshotReply{}

	rf.mu.RUnlock()
	// 向指定的节点发送安装快照的 RPC
	ok := rf.sendInstallSnapshotRPC(server, &installSnapshotArgs, &installSnapshotReply)
	if !ok {
		return
	}
	rf.mu.Lock()

	// 获取当前节点的任期号
	term := installSnapshotReply.Term
	// 如果当前节点的任期号小于接收到的回复中的任期号，更新当前节点的状态
	if term > rf.currentTerm {
		rf.state = Follower
		rf.votedFor = -1
		rf.currentTerm = term
		rf.persist()
	}

	// 如果当前节点不是 Leader 或者当前节点的任期号与接收到的回复中的任期号不同，直接返回
	if rf.state != Leader || term != rf.currentTerm {
		rf.mu.Unlock()
		return
	}

	rf.matchIndex[server] = rf.log[0].Index
	rf.nextIndex[server] = rf.matchIndex[server] + 1
	rf.mu.Unlock()
}

func (rf *Raft) sendInstallSnapshotRPC(server int, args *InstallSnapshotArgs, reply *InstallSnapshotReply) bool {
	ok := rf.peers[server].Call("Raft.InstallSnapshot", args, reply)
	return ok
}
